import socket
import time
from character import *
from util import *
from gameobject import objects
import pygame
import content
import random
try:
	import win32com
	import win32com.client
except:
	pass

PORT = 12345
LATENCY = 1

class ClientObject:
	def __init__(self, name, addr, port):
		self.name = name
		self.addr = addr
		self.port = port
		self.latency = 66
		self.frameLatency = LATENCY
		self.char = None
		self.ready = False

class Server:
	def __init__(self, name, listenPort = PORT):
		self.sock = socket.socket( socket.AF_INET, socket.SOCK_DGRAM )
		self.sock.bind( ("", listenPort) )
		self.port = listenPort
		self.pingStartTime = 0
		self.clients = []
		self.sock.settimeout(0.0)
		self.serverName = name
		try:
			natter = win32com.client.Dispatch("HNetCfg.NATUPnP")
			mappingPorts = natter.StaticPortMappingCollection
			mappingPorts.Add(PORT, "UDP", PORT, socket.gethostbyname(socket.gethostname()), True, "ExegesisServer")
		except:
			print("uPnP not enabled. You must open ports on your router manually for the server to be visible from the internet.")

	def notifyServerList(self):
		self.sock.sendto(str( {"type":"register", "name":self.serverName} ), ("morganquirk.com", 12321))

	def updateLatency(self):
		self.pingStartTime = time.time()
		self.broadcastPacket({"type":"ping"})

	def update(self, game):
		if len(self.clients) >= 2:
			allReady = True
			for c in self.clients:
				if c.ready:
					pass
				else:
					allReady = False
			if allReady:
				game.playersAllReady = True

		data = None
		addr = ""
		try:
			(data, addr) = self.sock.recvfrom(2048)
			ip = addr[0]
		except socket.error as e:
			return
		data = self.getPacketData(data)
		cid = data.get("cid", None)
		client = None
		if cid is not None:
			client = self.clients[cid]

		if data["type"] == "hello":
			print("User " + data["name"] + " connected from " + ip + ".")
			cid = len(self.clients)
			newClientPort = cid + self.port + 2
			co = ClientObject(data["name"], addr[0], addr[1])
			self.clients.append(co)
			char = Ken(content.images["ken"]["imagelist"])
			char.server = True
			loadBoxData("ken", char)
			char.x = len(game.chars) * 150 + 50
			char.y = 200
			char.initialize()
			game.chars.append(char)
			co.char = char
			self.sendPacket( ip, addr[1], {"type":"id", "cid":cid, "port":newClientPort})
			
		if data["type"] == "ready":
			print("User " + client.name + " is ready.")
			client.ready = True

		if data["type"] == "joystick":
			client.char.buttonsJustPressed[data["value"]] = True
			client.char.joystickDirection = data["value"]
			if(data["value"] == "F"):
				client.char.parryingTimer = 8

		if data["type"] == "button":
			client.char.buttonsJustPressed[data["value"]] = True
		
		if data["type"] == "pong":
			lat = (time.time() - self.pingStartTime)
			frameLatency = int(lat * 15.0)
			print(lat, frameLatency)
			client.latency = lat
			client.frameLatency = frameLatency
			self.sendPacket( client.addr, client.port, {"type":"latency", "value":lat})

	def broadcastPacket(self, data):
		for c in self.clients:
			self.sendPacket(c.addr, c.port, data)

	def sendPacket(self, addr, port, data):
		self.sock.sendto(str(data), (addr, port))

	def getPacketData(self, data):
		return eval(data)


	def getCharData(self, char):
		return {
			"x":char.x,
			"y":char.y,
			"joystickDirection":char.joystickDirection,
			"anim":char.anim.name,
			"animN":char.anim.n,
			"xVelocity":char.xVelocity,
			"yVelocity":char.yVelocity,
			"inAir":char.inAir,
			"health":char.health,
		}
		
	def sendDebugText(self, text, x, y):
		self.broadcastPacket({"type":"debugText", "text":text, "x":x, "y":y})

	def sendCharState(self):
		for c in self.clients:
			data = {"type":"state"}
			for otherc in self.clients:
				if otherc != c:
					data["opp"] = self.getCharData(otherc.char)
					data["opp"]["animN"] += c.frameLatency
			data["you"] = self.getCharData(c.char)
			data["you"]["animN"] += c.frameLatency

			self.sendPacket(c.addr, c.port, data)
			
	def spawn(self):
		for c in self.clients:
			data = {"type":"spawn"}
			for otherc in self.clients:
				if otherc != c:
					data["opp"] = {"charName":"ken"}
			data["you"] = {"charName":"ken"}
			print(c)
			self.sendPacket(c.addr, c.port, data)
			
	def sendCreateObject(self, name, owner, args):
		for c in self.clients:
			data = {"type":"create"}
			data["name"] = name
			if c.char == owner:
				data["owner"] = "you"
			else:
				data["owner"] = "opp"
			data["args"] = args
			self.sendPacket(c.addr, c.port, data)

class Client:
	def __init__(self, serverAddress, port = PORT):
		self.listenPort = port + 1
		self.addr = serverAddress
		self.sendPort = PORT
		self.latency = 66
		self.frameLatency = 1
		self.cid = None
		self.sock = socket.socket( socket.AF_INET, socket.SOCK_DGRAM )
		self.sock.bind( ("", self.listenPort) )
		self.sock.settimeout(0.0)

	def sendPacket(self, data):
		data["cid"] = self.cid
		self.sock.sendto(str(data), (self.addr, self.sendPort))

	def getPacketData(self, data):
		return eval(data)

	def connect(self):
		print("Attempting to connect on port " + str(self.sendPort))
		self.sendPacket({"type":"hello", "name":"user" + str(random.randint(0,1000) + 1000)})

	def update(self, game):
		data = None
		addr = ""
		try:
			(data, addr) = self.sock.recvfrom(1024)
		except(socket.error):
			return
		data = self.getPacketData(data)

		if data["type"] == "id":
			print("Client number " + str(data["cid"]) + ". Listening on port " + str(data["port"]))
			self.listenPort = data["port"]
			self.cid = data["cid"]
			self.sendPacket({"type":"ready"})

		if data["type"] == "spawn":
			game.localChar = self.createCharFromStateData(game, data["you"])
			game.netChar = self.createCharFromStateData(game, data["opp"])

			game.setupLocalCharacter()
			game.setupNetCharacter()

		if data["type"] == "state":
			self.updateCharFromStateData(game.localChar, data["you"], True)
			self.updateCharFromStateData(game.netChar, data["opp"])

		if data["type"] == "ping":
			self.sendPacket({"type":"pong"})

		if data["type"] == "latency":
			self.frameLatency = int(data["value"] * 15.0)
			self.latency = data["value"]

		if data["type"] == "create":
			ctor = objects[data["name"]]
			image = content.images["gameobjects"][data["name"] + ".png"]
			if data["owner"] == "you":
				owner = game.localChar
			else:
				owner = game.netChar
			obj = ctor(image, owner, *data["args"])
			game.objs.append(obj)
			
		if data["type"] == "debugText":
			game.showDebugText(data["text"], data["x"], data["y"])

	def sendJoystick(self, inp):
		self.sendPacket({"type":"joystick", "value":inp})

	def sendButton(self, inp):
		self.sendPacket({"type":"button", "value":inp})

	def createCharFromStateData(self, game, stateData):
		char = allCharacters[stateData["charName"]](content.images[stateData["charName"]]["imagelist"])
		game.chars.append(char)
		loadBoxData(stateData["charName"], char)
		return char

	def updateCharFromStateData(self, char, stateData, client = False):
		char.x = stateData["x"]
		char.y = stateData["y"]
		char.xVelocity = stateData["xVelocity"]
		char.yVelocity = stateData["yVelocity"]
		char.inAir = stateData["inAir"]
		char.health = stateData["health"]
		if not client:
			char.joystickDirection = stateData["joystickDirection"]
		if char.anim != char.animations[stateData["anim"]]:
			char.anim = char.animations[stateData["anim"]]
			char.anim.start()
		n = stateData["animN"]
		if abs(char.anim.n - n) > 1:
			char.anim.n = stateData["animN"]